package org.altervista.cp.thinjdbc.wrappers;

import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.Ref;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.LinkedList;

import org.altervista.cp.thinjdbc.util.LogHelper;

public abstract class PreparedStatement extends Statement implements
		java.sql.PreparedStatement {

	private static final LogHelper logger = new LogHelper(
			PreparedStatement.class);

	static PreparedStatement newInstance(java.sql.PreparedStatement ps,
			String sql) {
		PreparedStatement psw = _GENERATOR.generate(ps);
		psw._sql = sql;
		return psw;
	}

	public void clearParameters() {
		_parameters.clear();
		try {
			_unwrap().clearParameters();
		} catch (SQLException e) {
			throw new SQLRuntimeException(e);
		}
	}

	public void close() {
		try {
			java.sql.PreparedStatement ps = _unwrap();
			if (ps != null) {
				ps.close();
			}
		} catch (SQLException e) {
			throw new SQLRuntimeException(e);
		} finally {
			if (_pmdw != null) {
				_pmdw.close();
				_pmdw = null;
			}
			if (_rsmdw != null) {
				_rsmdw.close();
				_rsmdw = null;
			}
			_GENERATOR.release(this);
		}
	}

	public ResultSet executeQuery() {
		try {
			logger.debug("Executing query statement:\n{0}", this);

			ResultSet rw = ResultSet.newInstance(_unwrap().executeQuery());
			rw.setStatementWrapper(this);
			setResultSetWrapper(rw);
			return rw;
		} catch (SQLException e) {
			getConnection().rewrap(e);
			setParameters();
			return executeQuery();
		}
	}

	public boolean execute() {
		try {
			logger.debug("Executing statement:\n{0}", this);

			return _unwrap().execute();
		} catch (SQLException e) {
			getConnection().rewrap(e);
			setParameters();
			return execute();
		}
	}

	public int executeUpdate() {
		try {
			logger.debug("Executing update statement:\n{0}", this);

			return _unwrap().executeUpdate();
		} catch (SQLException e) {
			getConnection().rewrap(e);
			setParameters();
			return executeUpdate();
		}
	}

	public ResultSetMetaData getMetaData() {
		try {
			if (_rsmdw == null) {
				_rsmdw = ResultSetMetaData.newInstance(_unwrap().getMetaData());
			}
			return _rsmdw;
		} catch (SQLException e) {
			throw new SQLRuntimeException(e);
		}
	}

	public ParameterMetaData getParameterMetaData() {
		try {
			if (_pmdw == null) {
				_pmdw = ParameterMetaData.newInstance(_unwrap()
						.getParameterMetaData());
			}
			return _pmdw;
		} catch (SQLException e) {
			throw new SQLRuntimeException(e);
		}
	}

	public String getSQL() {
		return _sql;
	}

	public void setArray(int i, Array x) {
		new ArrayParameter(i, x).setParameter();
	}

	public void setAsciiStream(int parameterIndex, InputStream x, int length) {
		new AsciiStreamParameter(parameterIndex, x, length).setParameter();
	}

	public void setBigDecimal(int parameterIndex, BigDecimal x) {
		new BigDecimalParameter(parameterIndex, x).setParameter();
	}

	public void setBinaryStream(int parameterIndex, InputStream x, int length) {
		new BinaryStreamParameter(parameterIndex, x, length).setParameter();
	}

	public void setBlob(int i, Blob x) {
		new BlobParameter(i, x).setParameter();
	}

	public void setBoolean(int parameterIndex, boolean x) {
		new BooleanParameter(parameterIndex, x).setParameter();
	}

	public void setByte(int parameterIndex, byte x) {
		new ByteParameter(parameterIndex, x).setParameter();
	}

	public void setBytes(int parameterIndex, byte[] x) {
		new BytesParameter(parameterIndex, x).setParameter();
	}

	public void setCharacterStream(int parameterIndex, Reader reader, int length) {
		new CharacterStreamParameter(parameterIndex, reader, length)
				.setParameter();
	}

	public void setClob(int i, Clob x) {
		new ClobParameter(i, x).setParameter();
	}

	public void setDate(int parameterIndex, Date x, Calendar cal) {
		new DateParameter(parameterIndex, x, cal).setParameter();
	}

	public void setDate(int parameterIndex, Date x) {
		new DateParameter(parameterIndex, x).setParameter();
	}

	public void setDouble(int parameterIndex, double x) {
		new DoubleParameter(parameterIndex, x).setParameter();
	}

	public void setFloat(int parameterIndex, float x) {
		new FloatParameter(parameterIndex, x).setParameter();
	}

	public void setInt(int parameterIndex, int x) {
		new IntParameter(parameterIndex, x).setParameter();
	}

	public void setLong(int parameterIndex, long x) {
		new LongParameter(parameterIndex, x).setParameter();
	}

	public void setNull(int parameterIndex, int sqlType, String typeName) {
		new NullParameter(parameterIndex, sqlType, typeName).setParameter();
	}

	public void setNull(int parameterIndex, int sqlType) {
		new NullParameter(parameterIndex, sqlType).setParameter();
	}

	public void setObject(int parameterIndex, Object x, int targetSqlType,
			int scale) {
		new ObjectParameter(parameterIndex, x, targetSqlType, scale)
				.setParameter();
	}

	public void setObject(int parameterIndex, Object x, int targetSqlType) {
		new ObjectParameter(parameterIndex, x, targetSqlType).setParameter();
	}

	public void setObject(int parameterIndex, Object x) {
		new ObjectParameter(parameterIndex, x).setParameter();
	}

	public void setRef(int i, Ref x) {
		new RefParameter(i, x).setParameter();
	}

	public void setShort(int parameterIndex, short x) {
		new ShortParameter(parameterIndex, x).setParameter();
	}

	public void setString(int parameterIndex, String x) {
		new StringParameter(parameterIndex, x).setParameter();
	}

	public void setTime(int parameterIndex, Time x, Calendar cal) {
		new TimeParameter(parameterIndex, x, cal).setParameter();
	}

	public void setTime(int parameterIndex, Time x) {
		new TimeParameter(parameterIndex, x).setParameter();
	}

	public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) {
		new TimestampParameter(parameterIndex, x, cal).setParameter();
	}

	public void setTimestamp(int parameterIndex, Timestamp x) {
		new TimestampParameter(parameterIndex, x).setParameter();
	}

	public void setUnicodeStream(int parameterIndex, InputStream x, int length) {
		new UnicodeStreamParameter(parameterIndex, x, length).setParameter();
	}

	public void setURL(int parameterIndex, URL x) {
		new URLParameter(parameterIndex, x).setParameter();
	}

	public String toString() {
		boolean newline = false;
		StringBuffer sb = new StringBuffer(_sql);
		for (int i = 0; i < sb.length(); i++) {
			if (i % 80 == 0 && i != 0) {
				newline = true;
			}
			if (newline && sb.charAt(i) == ' ') {
				sb.replace(i, i + 1, "\n");
				newline = false;
			}
		}
		if (_parameters.size() != 0) {
			int lastch = sb.length() - 1;
			if (sb.charAt(lastch) == '\n') {
				sb.deleteCharAt(lastch);
			}
			sb.append(" - parameters: ").append(_parameters);
		}
		return sb.toString();
	}

	public abstract void addBatch();

	private void setParameters() {
		for (Parameter<?> p : _parameters) {
			p.setParameter();
		}
	}

	protected String _sql;

	private ParameterMetaData _pmdw;
	private ResultSetMetaData _rsmdw;
	private LinkedList<Parameter<?>> _parameters = new LinkedList<Parameter<?>>();

	protected abstract class Parameter<T> {
		public String toString() {
			return _value == null ? "NULL" : _value.toString();
		}

		protected Parameter(int parameterIndex, T value, Object... args) {
			_parameterIndex = parameterIndex;
			_value = value;
			_args = args;

			_parameters.add(this);
		}

		protected void setParameter() {
			try {
				set();
			} catch (SQLException e) {
				throw new SQLRuntimeException(e);
			}
		}

		protected abstract void set() throws SQLException;

		protected int _parameterIndex;
		protected T _value;
		protected Object[] _args;
	}

	protected class ArrayParameter extends Parameter<Array> {
		protected ArrayParameter(int parameterIndex, Array value) {
			super(parameterIndex, value);
		}

		protected void set() throws SQLException {
			_unwrap().setArray(_parameterIndex, _value);
		}
	}

	protected class AsciiStreamParameter extends Parameter<InputStream> {
		protected AsciiStreamParameter(int parameterIndex, InputStream value,
				int length) {
			super(parameterIndex, value, length);
		}

		protected void set() throws SQLException {
			_unwrap().setAsciiStream(_parameterIndex, _value,
					(Integer) _args[0]);
		}
	}

	protected class BigDecimalParameter extends Parameter<BigDecimal> {
		protected BigDecimalParameter(int parameterIndex, BigDecimal value) {
			super(parameterIndex, value);
		}

		protected void set() throws SQLException {
			_unwrap().setBigDecimal(_parameterIndex, _value);
		}
	}

	protected class BinaryStreamParameter extends Parameter<InputStream> {
		protected BinaryStreamParameter(int parameterIndex, InputStream value,
				int length) {
			super(parameterIndex, value, length);
		}

		protected void set() throws SQLException {
			_unwrap().setBinaryStream(_parameterIndex, _value,
					(Integer) _args[0]);
		}
	}

	protected class BlobParameter extends Parameter<Blob> {
		protected BlobParameter(int parameterIndex, Blob value) {
			super(parameterIndex, value);
		}

		protected void set() throws SQLException {
			_unwrap().setBlob(_parameterIndex, _value);
		}
	}

	protected class BooleanParameter extends Parameter<Boolean> {
		protected BooleanParameter(int parameterIndex, Boolean value) {
			super(parameterIndex, value);
		}

		protected void set() throws SQLException {
			_unwrap().setBoolean(_parameterIndex, _value);
		}
	}

	protected class ByteParameter extends Parameter<Byte> {
		protected ByteParameter(int parameterIndex, Byte value) {
			super(parameterIndex, value);
		}

		protected void set() throws SQLException {
			_unwrap().setByte(_parameterIndex, _value);
		}
	}

	protected class BytesParameter extends Parameter<byte[]> {
		protected BytesParameter(int parameterIndex, byte[] value,
				Object... args) {
			super(parameterIndex, value, args);
		}

		protected void set() throws SQLException {
			_unwrap().setBytes(_parameterIndex, _value);
		}
	}

	protected class CharacterStreamParameter extends Parameter<Reader> {
		protected CharacterStreamParameter(int parameterIndex, Reader value,
				int length) {
			super(parameterIndex, value, length);
		}

		protected void set() throws SQLException {
			_unwrap().setCharacterStream(_parameterIndex, _value,
					(Integer) _args[0]);
		}
	}

	protected class ClobParameter extends Parameter<Clob> {
		protected ClobParameter(int parameterIndex, Clob value) {
			super(parameterIndex, value);
		}

		protected void set() throws SQLException {
			_unwrap().setClob(_parameterIndex, _value);
		}
	}

	protected class DateParameter extends Parameter<Date> {
		protected DateParameter(int parameterIndex, Date value) {
			super(parameterIndex, value);
		}

		protected DateParameter(int parameterIndex, Date value,
				Calendar calendar) {
			super(parameterIndex, value, calendar);
		}

		protected void set() throws SQLException {
			if (_args == null || _args.length == 0) {
				_unwrap().setDate(_parameterIndex, _value);
			} else {
				_unwrap().setDate(_parameterIndex, _value, (Calendar) _args[0]);
			}
		}
	}

	protected class DoubleParameter extends Parameter<Double> {
		protected DoubleParameter(int parameterIndex, Double value) {
			super(parameterIndex, value);
		}

		protected void set() throws SQLException {
			_unwrap().setDouble(_parameterIndex, _value);
		}
	}

	protected class FloatParameter extends Parameter<Float> {
		protected FloatParameter(int parameterIndex, Float value) {
			super(parameterIndex, value);
		}

		protected void set() throws SQLException {
			_unwrap().setFloat(_parameterIndex, _value);
		}
	}

	protected class IntParameter extends Parameter<Integer> {
		protected IntParameter(int parameterIndex, Integer value) {
			super(parameterIndex, value);
		}

		protected void set() throws SQLException {
			_unwrap().setInt(_parameterIndex, _value);
		}
	}

	protected class LongParameter extends Parameter<Long> {
		protected LongParameter(int parameterIndex, Long value) {
			super(parameterIndex, value);
		}

		protected void set() throws SQLException {
			_unwrap().setLong(_parameterIndex, _value);
		}
	}

	protected class NullParameter extends Parameter<Object> {
		protected NullParameter(int parameterIndex, int sqlType) {
			super(parameterIndex, null, sqlType);
		}

		protected NullParameter(int parameterIndex, int sqlType, String typeName) {
			super(parameterIndex, null, sqlType, typeName);
		}

		protected void set() throws SQLException {
			if (_args.length == 1) {
				_unwrap().setNull(_parameterIndex, (Integer) _args[0]);
			} else {
				_unwrap().setNull(_parameterIndex, (Integer) _args[0],
						(String) _args[1]);
			}
		}
	}

	protected class ObjectParameter extends Parameter<Object> {
		protected ObjectParameter(int parameterIndex, Object value) {
			super(parameterIndex, value);
		}

		protected ObjectParameter(int parameterIndex, Object value, int sqlType) {
			super(parameterIndex, value, sqlType);
		}

		protected ObjectParameter(int parameterIndex, Object value,
				int sqlType, int scale) {
			super(parameterIndex, value, sqlType, scale);
		}

		protected void set() throws SQLException {
			if (_args == null || _args.length == 0) {
				_unwrap().setObject(_parameterIndex, _value);
			} else if (_args.length == 1) {
				_unwrap()
						.setObject(_parameterIndex, _value, (Integer) _args[0]);
			} else {
				_unwrap().setObject(_parameterIndex, _value,
						(Integer) _args[0], (Integer) _args[1]);
			}
		}
	}

	protected class RefParameter extends Parameter<Ref> {
		protected RefParameter(int parameterIndex, Ref value) {
			super(parameterIndex, value);
		}

		protected void set() throws SQLException {
			_unwrap().setRef(_parameterIndex, _value);
		}
	}

	protected class ShortParameter extends Parameter<Short> {
		protected ShortParameter(int parameterIndex, Short value) {
			super(parameterIndex, value);
		}

		protected void set() throws SQLException {
			_unwrap().setShort(_parameterIndex, _value);
		}
	}

	protected class StringParameter extends Parameter<String> {
		protected StringParameter(int parameterIndex, String value) {
			super(parameterIndex, value);
		}

		protected void set() throws SQLException {
			_unwrap().setString(_parameterIndex, _value);
		}
	}

	protected class TimeParameter extends Parameter<Time> {
		protected TimeParameter(int parameterIndex, Time value) {
			super(parameterIndex, value);
		}

		protected TimeParameter(int parameterIndex, Time value,
				Calendar calendar) {
			super(parameterIndex, value, calendar);
		}

		protected void set() throws SQLException {
			if (_args == null || _args.length == 0) {
				setTime(_parameterIndex, _value);
			} else {
				setTime(_parameterIndex, _value, (Calendar) _args[0]);
			}
		}
	}

	protected class TimestampParameter extends Parameter<Timestamp> {
		protected TimestampParameter(int parameterIndex, Timestamp value) {
			super(parameterIndex, value);
		}

		protected TimestampParameter(int parameterIndex, Timestamp value,
				Calendar calendar) {
			super(parameterIndex, value, calendar);
		}

		protected void set() throws SQLException {
			if (_args == null || _args.length == 0) {
				_unwrap().setTimestamp(_parameterIndex, _value);
			} else {
				_unwrap().setTimestamp(_parameterIndex, _value,
						(Calendar) _args[0]);
			}
		}
	}

	protected class UnicodeStreamParameter extends Parameter<InputStream> {
		protected UnicodeStreamParameter(int parameterIndex, InputStream value,
				int length) {
			super(parameterIndex, value, length);
		}

		@SuppressWarnings("deprecation")
		protected void set() throws SQLException {
			_unwrap().setUnicodeStream(_parameterIndex, _value,
					(Integer) _args[0]);
		}
	}

	protected class URLParameter extends Parameter<URL> {
		protected URLParameter(int parameterIndex, URL value) {
			super(parameterIndex, value);
		}

		protected void set() throws SQLException {
			_unwrap().setURL(_parameterIndex, _value);
		}
	}

	private java.sql.PreparedStatement _unwrap() {
		return (java.sql.PreparedStatement) unwrap();
	}

	private static final _ProxyGenerator<PreparedStatement> _GENERATOR = new _ProxyGenerator<PreparedStatement>(
			PreparedStatement.class);

}
